import os
import sys
sys.path.append(os.path.abspath(os.path.dirname(__file__)) + "/..")
from utils.mqtt.mqtt import MqttAdmin
import signal
from misc import get_wan_info, Notify
from common_msg import *
from utils.thread.thread import thread_write_add
from utils.thread.thread import thread_except_add
from utils.thread.thread import thread_timer_add
from utils.thread.thread import thread_run
from utils.thread.thread import thread_del
from queue import Queue
from utils.thread.thread import thread_read_add
import time
import struct
import socket
from fcntl import ioctl
from select import select
from threading import Thread
from ipaddress import ip_network

from local_log import logger

PASSWORD = b'4fb88ca224e'
BUFFER_SIZE = 4096
MTU = 1500
TUNSETIFF = 0x400454ca
IFF_TUN = 0x0001
IFF_TAP = 0x0002

MQTT_TOPIC_SERVER_DATA = 'server_data'
BIND_ADDRESS = '119.28.43.241'
MQTT_BROKER = BIND_ADDRESS
MQTT_PORT = 1883
KEEPALIVE_TIME = 10

def get_wan_ip():
    ip, inf = get_wan_info()
    return ip if ip else '192.168.1.1'

class TunConn(object):
    def __init__(self, conf):
        logger.debug(f'enter tunconn init{type(conf)}')
        super().__init__()
        self.__conf = conf
        self.__th_read = None
        self.__th_write = None
        self.__list_write = []
        self.__tunfd, self.__tunName = self.__createTunnel()
        self.tunAddr = '192.168.255.100'
        self.tunMask = '255.255.255.0'
        self.__startTunnel(self.__tunName)
        os.set_blocking(self.__tunfd, False)
        self.__reset_tun_read()

    def __del__(self):
        if self.__th_read:
            thread_del(self.__th_read)
            self.__th_read = None
        if self.__th_write:
            thread_del(self.__th_write)
            self.__th_write = None
        self.__list_write = []
        if self.__tunfd:
            os.close(self.__tunfd)
            self.__tunfd = None

    def __createTunnel(self, tunName='tun%d', tunMode=IFF_TUN):
        tunfd = os.open("/dev/net/tun", os.O_RDWR)
        ifn = ioctl(
            tunfd,
            TUNSETIFF,
            struct.pack(
                b"16sH",
                tunName.encode(),
                tunMode))
        tunName = ifn[:16].decode().strip("\x00")
        return tunfd, tunName

    def __startTunnel(self, tunName):
        with os.popen('ifconfig %s %s netmask %s mtu %s up' %
                      (tunName, self.tunAddr, self.tunMask, MTU)) as f:
            logger.debug(f.readlines())

    def reStartTunnel(self, tunAddr, tunMask):
        logger.debug(f'tunaddr need restart')
        self.tunAddr = tunAddr
        self.tunMask = tunMask
        self.__startTunnel(self.__tunName)

    @staticmethod
    def __tun_read(my_self, fd, args):
        my_self.__th_read = None
        data = os.read(fd, BUFFER_SIZE)
        my_self.tun_receive(data)
        my_self.__reset_tun_read()

    @staticmethod
    def __tun_write(my_self, fd, args):
        my_self.__th_write = None
        # logger.debug(f'enter __tun_write !!!!!!!!!!!')
        if len(my_self.__list_write) == 0:
            return
        data = my_self.__list_write.pop(0)
        ret = os.write(fd, data)
        my_self.__reset_tun_write()

    def __reset_tun_read(self):
        if self.__th_read:
            thread_del(self.__th_read)
            self.__th_read = None
        self.__th_read = thread_read_add(
            TunConn.__tun_read, self, self.__tunfd)

    def __reset_tun_write(self):
        # logger.debug(f'enter __reset_tun_write')
        if self.__th_write:
            thread_del(self.__th_write)
            self.__th_write = None
        self.__th_write = thread_write_add(
            TunConn.__tun_write, self, self.__tunfd)

    def tun_write(self, data):
        if len(self.__list_write) == 0:
            self.__reset_tun_write()
        self.__list_write.append(data)

    def tun_receive(self, data):
        logger.debug(f'this tun_receive func is for subclass use, so goto here is err!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!')

class Client(TunConn, MqttAdmin, Notify):
    def __init__(self):
        logger.debug(f'enter client init')
        super().__init__(self)
        # mqtt连接
        self.mqtt_connect(MQTT_BROKER, MQTT_PORT, 'tls/ca.crt')
        logger.debug(f'mqtt clientid is {self.mqtt_client_id()}')
        self.msg_queue = Queue()
        #开个心跳定时
        self.th_keepalive_timer = None
        self.reset_keepalive_timer()

    def mqtt_on_connect(self, client, userdata, flags, rc):
        if rc == 0:
            logger.debug(
                f"Connected to MQTT Broker! goto register top {self.mqtt_client_id()}")
            self.mqtt_subscribe(self.mqtt_client_id())
        else:
            logger.debug("Failed to connect, return code %d\n", rc)

    def mqtt_on_message(self, client, userdata, msg):
        self.msg_queue.put_nowait(msg)
        self.notify()

    def notify_proc(self, data: bytes):
        while True:
            if self.msg_queue.empty():
                return
            msg = self.msg_queue.get_nowait()
            data = msg.payload
            cmsg = CommMsg()
            if cmsg.unpack(data) is False:
                logger.debug(
                    f'mqtt_on_message get error msg!!!!!!! len is {len(data)}')
                return
            logger.debug(f'mqtt_on_message data len  {len(data)}')
            # logger.debug(f'mqtt_on_message data len  {len(data)} and uuid is {cmsg.uuid.decode()} and cmd is {cmsg.cmd}')
            if cmsg.cmd == MSG_AUTH_RESULT:
                logger.debug(f'get tunaddr {cmsg.data}')
                info = cmsg.data.decode()
                aa = info.split(';')
                logger.debug(f'get gw={aa[0]} and tunaddr={aa[1]} and tunmask={aa[2]}')
                if self.tunAddr != aa[1]:
                    self.reStartTunnel(aa[1], aa[2])
                    # 路由处理
                    route_list = [
                        'ip route add %s via %s' %(BIND_ADDRESS, get_wan_ip()),
                        'ip route add 0.0.0.0/1 via %s' % aa[0],
                        'ip route add 128.0.0.0/1 via %s' % aa[0],
                    ]
                    for it in route_list:
                        logger.debug(f'route cmd is {it}\n')
                        with (os.popen(it)) as f:
                            logger.debug(f.readlines())
                    return
            if cmsg.cmd != MSG_DATA:
                logger.debug(f'get err cmd {cmsg.cmd}')
                return
            self.tun_write(cmsg.data)

    def tun_receive(self, data):
        # dump_hex('client run recv', data)
        cmsg = CommMsg(MSG_DATA, self.mqtt_client_id().encode(), data)
        self.mqtt_publish(MQTT_TOPIC_SERVER_DATA, cmsg.pack())

    def reset_keepalive_timer(self):
        if self.th_keepalive_timer:
            thread_del(self.th_keepalive_timer)
            self.th_keepalive_timer = None
        self.th_keepalive_timer = thread_timer_add(Client.do_keepalive, self, KEEPALIVE_TIME)
    
    @staticmethod
    def do_keepalive(lmy_self, fd, args):
        lmy_self.th_keepalive_timer = 0
        lmy_self.reset_keepalive_timer()
        cmsg = CommMsg(MSG_KEEPLIVE, lmy_self.mqtt_client_id().encode())
        lmy_self.mqtt_publish(MQTT_TOPIC_SERVER_DATA, cmsg.pack())

    def run_forever(self):
        thread_run()

if __name__ == '__main__':
    signal.signal(signal.SIGPIPE, signal.SIG_IGN)
    signal.signal(signal.SIGCHLD, signal.SIG_IGN)
    logger.debug(f'cient __mro__ is {Client.__mro__}')
    client = Client()
    client.run_forever()
